﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Security.Cryptography;

namespace homeIMSService.Model
{
    //class represents static storage for all logged in users and houses login/logout capability. only 1 instance should be up per WCF session
    class service:DBconnection.dbConnector
    {
        //private vars used to store active users
        private List<user> _activeUsers;

        //public vars to have access
        public List<user> ActiveUsers
        {
            get { return _activeUsers; }
        }
        //contructor used to create variables for storing logged in users
        public service()
        {
            //to fill _activeUsers, instantiate
            _activeUsers = new List<user>();
        }

        //method used to login a user
        public userInfo login(string userName, string password)
        {
            
            //set return value
            userInfo isValid;

            //send through method
            isValid = createUser(userName, password);
            return isValid;
            
        }

        //method used to register a new user
        public int register(userInfo userInformation)
        {
            
            //create SqlParameters for db query
            List<SqlParameter> liSqlParams = new List<SqlParameter>();

            //take each string from userInformation and store it as SqlParameter
            
            SqlParameter uName = new SqlParameter("@userName", userInformation.userName);

            //hash password using algorithm

            //generate salt
            byte[] salt = GenerateSalt(userInformation.userName.ToString());
            string hashedPassword = HashPassword(userInformation.password.ToString(), salt);

            //store hashed password
            
            SqlParameter uPass = new SqlParameter("@userPass", hashedPassword);
            SqlParameter address = new SqlParameter("@address", userInformation.address);
            SqlParameter city = new SqlParameter("@city", userInformation.city);
            SqlParameter state = new SqlParameter("@state", userInformation.state);
            SqlParameter zip = new SqlParameter("@zip", System.Data.SqlTypes.SqlInt32.Null);
            SqlParameter phone = new SqlParameter("@phoneNum", System.Data.SqlTypes.SqlInt32.Null);
            //for int data types, check for null values and insert 0
            if (userInformation.zip != "")
            {
                zip = new SqlParameter("@zip", Convert.ToInt32(userInformation.zip));
            }
            if (userInformation.phoneNumber != "")
            {
                phone = new SqlParameter("@phoneNum", Convert.ToInt32(userInformation.phoneNumber));
            }
            
            //add SqlParameters to liSqlParams
            
            liSqlParams.Add(uName);
            liSqlParams.Add(uPass);
            liSqlParams.Add(address);
            liSqlParams.Add(city);
            liSqlParams.Add(state);
            liSqlParams.Add(zip);
            liSqlParams.Add(phone);

            //store information using dbConnector
            DataTable resultDt = SendDataTableRequest("spInsertUser", liSqlParams);
            
            //variable to return
            int result = 0;
            //take results from the DataTable
            switch (resultDt.Rows.Count)
            {
                case 1:
                    {
                        //set result to 1
                        result = 1;
                    }
                    break;
                case 0:
                    {
                        //set result to 0
                        result = 0;
                    }
                    break;
            }
            //send result of update
            return result;
        }

        //method used to create user and add to active user list
        private userInfo createUser(string userName, string password)
        {
            //create user variable that will return be returned
            userInfo returnedUser = new userInfo();

            //send through method for verification
            int result = Verify(userName, password);

            //if statement to check for userID validation
            if (result > 0)
            {
                //create new user
                user newUser = new user(userName, password, result);

                //add user to _activeUsers
                _activeUsers.Add(newUser);

                //get all info to return through data contract
                returnedUser = newUser.getAllInfo();

                return returnedUser;
            }
            else
            {
                return returnedUser;
            }
            
        }

        //method will verify userName and password by comparing password to record in db
        private int Verify(string userName, string password)
        {
            //private variables that will store password and userID
            string hashedPassword = "";
            int userID = 0;
            //run method to retrieve record from database
            //create SqlParameters to access db for information
            List<SqlParameter> liSqlParams = new List<SqlParameter>();
            SqlParameter un = new SqlParameter("@userName", userName);
            liSqlParams.Add(un);

            //access db to retrieve password and userID
            DataTable dtResults = null;
            dtResults = SendDataTableRequest("dbo.spGetUserPassword", liSqlParams);

            //if statement to check if record exists
            if (dtResults.Rows.Count > 0)
            {
                //go through row and compare password after hashing algorithm
                
                foreach (DataRow row in dtResults.Rows)
                {
                    hashedPassword = row["UserPassword"].ToString();
                    userID = Convert.ToInt32(row["PKuserID"]);
                }

                //send through VerifyHashedPassword
               
                bool result = VerifyHashedPassword(hashedPassword, password);

                //check result
                switch (result)
                {
                    case true:
                        {
                            //if true, do nothing
                        }
                        break;
                    case false:
                        {
                            //if false, set userID to 0 for no record
                            userID = 0;
                        }
                        break;
                }  
            }
            //return userID
            return userID;
        }

        //method will check userName to see if it has been used.
        public bool CheckUserName(string userName)
        {
            //create param list for SQL
            List<SqlParameter> liSqlParam = new List<SqlParameter>();

            //create sql param with userName
            SqlParameter param = new SqlParameter("@userName", userName);

            //add param to list
            liSqlParam.Add(param);

            //create result variable to check result
            DataTable result = SendDataTableRequest("spCheckUserName", liSqlParam);

            //check result
            
            if (result.Rows.Count == 0)
            {
                //if successful, return true
                return true;
            }
            else
            {
                //if not successful, return false
                return false;
            }
                
            
        }

        
        //metho will return data table of asset types
        public assetTypeList getAssetTypes()
        {
            //create list of sql parameters
            List<SqlParameter> liSqlParam = new List<SqlParameter>();

            //send through method
            DataTable typeList = SendDataTableRequest("spGetAssetTypes", liSqlParam);

            //create assetTypeList variable
            assetTypeList newTypeList = new assetTypeList();

            //create assetType variable
            List<assetType> assetTypes = new List<assetType>();

            //loop through types
            foreach (DataRow row in typeList.Rows)
            {
                //create assetType DataContract
                assetType newType = new assetType();

                //fill newType
                newType.type = row["type"].ToString();
                newType.typeID = Convert.ToInt32(row["PKtypeLU"]);

                //store newType in list
                assetTypes.Add(newType);

            }
            //add assetTypes to newTypeList
            newTypeList.assetTypes = assetTypes;
            return newTypeList;
        }

        //method will return data table of quality listings
        public assetQualityList getQualityList()
        {
            ///create list of sql parameters
            List<SqlParameter> liSqlParam = new List<SqlParameter>();

            //send through method
            DataTable typeList = SendDataTableRequest("spGetQualityList", liSqlParam);

            //create list of qualities
            assetQualityList qualityList = new assetQualityList();

            //create assetQuality variable
            List<assetQuality> assetQualities = new List<assetQuality>();

            //loop through Qualities
            foreach (DataRow row in typeList.Rows)
            {
                //create quality DataContract
                assetQuality newQuality = new assetQuality();

                //fill quality
                newQuality.quality = row["quality"].ToString();
                newQuality.qualityID = Convert.ToInt32(row["PKqualityLU"]);
                
                //store newQuality in list
                assetQualities.Add(newQuality);

            }
            //add assetQualities to qualityList
            qualityList.assetQualities = assetQualities;

            //return filled list
            return qualityList;
        }

        //                                            //
        //     Hashing Algorithms and Methods         //
        //     TODO: PUT BELOW IN A CLASS BY ITSELF   //
        // The following constants may be changed without breaking existing hashes.
        public const int SALT_BYTES = 24;
        public const int HASH_BYTES = 24;
        public const int PBKDF2_ITERATIONS = 1000;

        public const int ITERATION_INDEX = 1;
        public const int SALT_INDEX = 2;
        public const int PBKDF2_INDEX = 3;

        /// <summary>
        /// Creates a salted PBKDF2 hash of the password.
        /// </summary>
        /// <param name="password">The password to hash.</param>
        /// <returns>The hash of the password.</returns>
        public string HashPassword(string password, byte[] salt)
        {
            // Generate a random salt
            RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
            
            //to generate unique salt for every hash, pull from information

            // Hash the password and encode the parameters
            byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES);
            return "sha1:" + PBKDF2_ITERATIONS + ":" +
                Convert.ToBase64String(salt) + ":" +
                Convert.ToBase64String(hash);
        }

        /// <summary>
        /// Validates a password given a hash of the correct one.
        /// </summary>
        /// <param name="password">The password to check.</param>
        /// <param name="goodHash">A hash of the correct password.</param>
        /// <returns>True if the password is correct. False otherwise.</returns>
        public bool VerifyHashedPassword(string goodHash, string password)
        {
            // Extract the parameters from the hash
            char[] delimiter = { ':' };
            string[] split = goodHash.Split(delimiter);
            int iterations = Int32.Parse(split[ITERATION_INDEX]);
            byte[] salt = Convert.FromBase64String(split[SALT_INDEX]);
            byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]);

            byte[] testHash = PBKDF2(password, salt, iterations, hash.Length);

            //test
            
            if (testHash.Length == hash.Length)
            {
                for (int i = 0; i < testHash.Length; i++)
                {
                    if (testHash[i] != hash[i])
                    {
                        return false;
                    }
                }
                
            }
            return true;
                       
            //return SlowEquals(hash, testHash);
        }

        //generate salt
        private byte[] GenerateSalt(string userName)
        {

            byte[] bytes = new byte[userName.Length * sizeof(char)];
            System.Buffer.BlockCopy(userName.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;


        }

        /// <summary>
        /// Compares two byte arrays in length-constant time. This comparison
        /// method is used so that password hashes cannot be extracted from
        /// on-line systems using a timing attack and then attacked off-line.
        /// </summary>
        /// <param name="a">The first byte array.</param>
        /// <param name="b">The second byte array.</param>
        /// <returns>True if both byte arrays are equal. False otherwise.</returns>
        private static bool SlowEquals(byte[] a, byte[] b)
        {
            uint diff = (uint)a.Length ^ (uint)b.Length;
            for (int i = 0; i < a.Length && i < b.Length; i++)
                diff |= (uint)(a[i] ^ b[i]);
            return !(Convert.ToBoolean(diff));
        }

        /// <summary>
        /// Computes the PBKDF2-SHA1 hash of a password.
        /// </summary>
        /// <param name="password">The password to hash.</param>
        /// <param name="salt">The salt.</param>
        /// <param name="iterations">The PBKDF2 iteration count.</param>
        /// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
        /// <returns>A hash of the password.</returns>
        private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
        {
            Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
            pbkdf2.IterationCount = iterations;
            return pbkdf2.GetBytes(outputBytes);
        }      
    }
}
